﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Xml.Linq;
using Xenta.Utils;

namespace Xenta.Web
{
    /// <summary>
    /// Represents the extended HttpApplication.
    /// </summary>
    public abstract class ExtApplication : HttpApplication
    {
        #region Methods

        /// <summary>
        /// Executes custom initialization code.
        /// </summary>
        public override void Init()
        {
            base.Init();

            BeginRequest += OnBeginRequest;
            EndRequest += OnEndRequest;
            AuthorizeRequest += OnAuthorizeRequest;
            Error += OnError;

            AuthenticateRequest += OnAuthenticateRequest;
            AcquireRequestState += OnAcquireRequestState;
            MapRequestHandler += OnMapRequestHandler;
            ReleaseRequestState += OnReleaseRequestState;
            ResolveRequestCache += OnResolveRequestCache;
            UpdateRequestCache += OnUpdateRequestCache;

            PreRequestHandlerExecute += OnPreRequestHandlerExecute;
            PreSendRequestContent += OnPreSendRequestContent;
            PreSendRequestHeaders += OnPreSendRequestHeaders;

            PostAuthenticateRequest += OnPostAuthenticateRequest;
            PostAcquireRequestState += OnPostAcquireRequestState;
            PostAuthorizeRequest += OnPostAuthorizeRequest;
            PostMapRequestHandler += OnPostMapRequestHandler;
            PostLogRequest += OnPostLogRequest;
            PostReleaseRequestState += OnPostReleaseRequestState;
            PostRequestHandlerExecute += OnPostRequestHandlerExecute;
            PostResolveRequestCache += OnPostResolveRequestCache;
            PostUpdateRequestCache += OnPostUpdateRequestCache;
        }

        #endregion

        #region Handlers

        protected virtual void OnBeginRequest(object sender, EventArgs e)
        {
            if(IsContentRequest(Context))
                return;
            var ctx = ServiceContext(Context);
            Service.Instantiate(ctx)
                .Initialize();
        }

        protected virtual void OnEndRequest(object sender, EventArgs e)
        {
        }

        protected virtual void OnError(object sender, EventArgs e)
        {
            if(IsContentRequest(Context))
                return;

            var lastError = Server.GetLastError();
            if(lastError == null)
                return;

            try
            {
                var svc = Context.Svc();
                svc.ProcessOperation("CreateLogRecord", new
                {
                    Source = String.Format("{0}({1}): {2}", 
                        GetType().Name, 
                        svc.Context.Infrastructure, 
                        lastError.Source),
                    Description = lastError.Message,
                    Text = lastError.ToString(),
                    Flags = "High,Error"
                });
            }
            catch(Exception ex)
            {
                Trace.TraceError(ex.Message);
            }
        }

        protected virtual void OnUpdateRequestCache(object sender, EventArgs e)
        {
        }

        protected virtual void OnResolveRequestCache(object sender, EventArgs e)
        {
        }

        protected virtual void OnReleaseRequestState(object sender, EventArgs e)
        {
        }

        protected virtual void OnMapRequestHandler(object sender, EventArgs e)
        {
        }

        protected virtual void OnAuthenticateRequest(object sender, EventArgs e)
        {
            if(IsContentRequest(Context))
                return;
            Context.Svc().Authenticate();
        }

        protected virtual void OnAuthorizeRequest(object sender, EventArgs e)
        {
        }

        protected virtual void OnPreSendRequestHeaders(object sender, EventArgs e)
        {
        }

        protected virtual void OnPreSendRequestContent(object sender, EventArgs e)
        {
        }

        protected virtual void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
        }

        protected virtual void OnAcquireRequestState(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostAuthenticateRequest(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostUpdateRequestCache(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostResolveRequestCache(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostRequestHandlerExecute(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostReleaseRequestState(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostLogRequest(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostMapRequestHandler(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostAuthorizeRequest(object sender, EventArgs e)
        {
        }

        protected virtual void OnPostAcquireRequestState(object sender, EventArgs e)
        {
        }

        #endregion

        #region Utilities

        protected void Application_Start()
        {
            SetupViewEngine();
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
            RegisterFilters(GlobalFilters.Filters);
            RegisterBundles(BundleTable.Bundles);
            /* TODO: it is false, because if it is true, the image paths are being broken (e.g. /bundles/css/images/ui-bg_flat_75_ffffff_40x100.png = HTTP 404).
             * Possible solution - use the minified bundles for the not DEBUG
             */
            BundleTable.EnableOptimizations = false;
            RouteTable.Routes.RouteExistingFiles = false;
        }

        protected virtual void RegisterFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
        }

        protected virtual void RegisterRoutes(RouteCollection routes)
        {
            var path = Server.MapPath("~/Routes.xml");
            if(!File.Exists(path))
                return;
            var xRoutes = XElement.Load(path);
            foreach(var i in xRoutes.Elements("add"))
            {
                var xDefaults = i.Element("defaults");
                var defaults = new RouteValueDictionary();
                if(xDefaults != null)
                {
                    foreach(var j in xDefaults.Elements())
                        defaults.Add(j.Name.LocalName, j.Value);
                }

                var xConstraints = i.Element("constraints");
                var constraints = new RouteValueDictionary();
                if(xConstraints != null)
                {
                    foreach(var j in xConstraints.Elements())
                        constraints.Add(j.Name.LocalName, j.Value);
                }

                var xArea = i.Element("area");
                var xNamespaces = i.Elements("namespace");
                var dataTokens = new RouteValueDictionary();
                if(xArea != null)
                    dataTokens.Add("area", xArea.Value);
                if(xNamespaces.Any())
                    dataTokens.Add("Namespaces", xNamespaces.Select(x => x.Value).ToArray());

                var xUrl = i.Attribute("url");
                var xName = i.Attribute("name");
                var xHandler = i.Element("handler");
                var tHandler = typeof(MvcRouteHandler);
                if(xHandler != null)
                    tHandler = Type.GetType(xHandler.Value, true);
                var handler = Activator.CreateInstance(tHandler)
                    as IRouteHandler;
                var route = new Route(xUrl.Value,
                    defaults,
                    constraints,
                    dataTokens,
                    handler);

                routes.Add(xName != null 
                    ? xName.Value 
                    : String.Empty, 
                    route);
            }
        }

        protected virtual void RegisterBundles(BundleCollection bundles)
        {
            var path = Server.MapPath("~/Bundles.xml");
            if(!File.Exists(path))
                return;
            var xBundles = XElement.Load(path)
                .Elements("add");
            if(!xBundles.Any())
                return;
            foreach(var xBundle in xBundles)
            {
                var includes = xBundle.Elements("inc")
                    .Select(x => x.Attribute("vPath").Value
                        .ToLowerInvariant())
                    .ToArray();
                if(!includes.Any())
                    continue;

                string type = xBundle.Attribute("type").Value;
                type = type.ToLowerInvariant();
                string vPath = xBundle.Attribute("vPath").Value;
                vPath = vPath.ToLowerInvariant();
                Bundle bundle = null;

                switch(type)
                {
                    case "style":
                        bundle = new StyleBundle(vPath);
                        break;
                    case "script":
                        bundle = new ScriptBundle(vPath);
                        break;
                }
                if(bundle == null)
                    continue;

                bundle.Orderer = new FifoBundleOrderer();
                bundle.Include(includes);

                bundles.Add(bundle);
            }
        }

        protected virtual void SetupViewEngine()
        {
        }

        protected abstract IServiceContext ServiceContext(HttpContext context);

        protected abstract bool IsContentRequest(HttpContext context);

        public override string GetVaryByCustomString(HttpContext context, string custom)
        {
            switch(custom)
            {
                case "locale":
                    return JDM.String(context.Svc().Localization.Code);
                case "account":
                    return context.Svc().IsAuthenticated
                        ? JDM.String(context.Svc().Account.Guid)
                        : "guest";
            }
            return base.GetVaryByCustomString(context, custom);
        }

        #endregion
    }

    public class FifoBundleOrderer : IBundleOrderer
    {
        public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
        {
            return files;
        }
    }
}